 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import org.eclipse.core.commands.Command;
 import org.eclipse.core.commands.ParameterizedCommand;
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.bindings.Trigger;
 import org.eclipse.jface.bindings.TriggerSequence;
 import org.eclipse.jface.bindings.keys.KeyStroke;
 import org.eclipse.jface.bindings.keys.SWTKeySupport;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.FocusEvent;
 import org.eclipse.swt.events.FocusListener;
 import org.eclipse.swt.events.HelpEvent;
 import org.eclipse.swt.events.HelpListener;
 import org.eclipse.swt.events.KeyEvent;
 import org.eclipse.swt.events.KeyListener;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseListener;
 import org.eclipse.swt.events.TraverseEvent;
 import org.eclipse.swt.events.TraverseListener;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableColumn;
 import org.eclipse.swt.widgets.TableItem;
 import org.eclipse.ui.IEditorReference;
 import org.eclipse.ui.IWorkbenchPage;
 import org.eclipse.ui.IWorkbenchPart;
 import org.eclipse.ui.IWorkbenchPartReference;
 import org.eclipse.ui.IWorkbenchWindow;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.commands.ICommandService;
 import org.eclipse.ui.contexts.IContextService;
 import org.eclipse.ui.keys.IBindingService;

 /**
  * Implements a action to enable the user switch between parts using keyboard.
  */
 public class CyclePartAction extends PageEventAction {

     /**
      * The list of key bindings for the backward command when it is open. This
      * value is <code>null</code> if the dialog is not open.
      */
     private TriggerSequence[] backwardTriggerSequences = null;

     private ParameterizedCommand commandBackward = null;

     private ParameterizedCommand commandForward = null;

     protected boolean forward;

     /**
      * The list of key bindings for the forward command when it is open. This
      * value is <code>null</code> if the dialog is not open.
      */
     private TriggerSequence[] forwardTriggerSequences = null;

     private Object selection;

     /**
      * Creates a CyclePartAction.
      *
      * @param window
      * The workbench window on which the dialog should be created.
      * @param next
      * Whether the action is to move to the next part (as opposed to
      * the previous).
      */
     public CyclePartAction(IWorkbenchWindow window, boolean next) {
         super("", window); //$NON-NLS-1$
 forward = next;
         setText();
         updateState();
     }

     /**
      * Activate the selected item.
      *
      * @param page
      * the page
      * @param selectedItem
      * the selected item
      */
     public void activate(IWorkbenchPage page, Object selectedItem) {
         if (selectedItem != null) {
             if (selectedItem instanceof IEditorReference) {
                 page.setEditorAreaVisible(true);
             }

             IWorkbenchPart part = ((IWorkbenchPartReference) selectedItem)
                     .getPart(true);

             if (part != null) {
                 page.activate(part);
             }
         }
     }

     /**
      * Add all views to the dialog in the activation order
      */
     protected void addItems(Table table, WorkbenchPage page) {
         IWorkbenchPartReference refs[] = page.getSortedParts();
         boolean includeEditor = true;

         for (int i = refs.length - 1; i >= 0; i--) {
             if (refs[i] instanceof IEditorReference) {
                 if (includeEditor) {
                     IEditorReference activeEditor = (IEditorReference) refs[i];
                     TableItem item = new TableItem(table, SWT.NONE);
                     item.setText(WorkbenchMessages.CyclePartAction_editor);
                     item.setImage(activeEditor.getTitleImage());
                     item.setData(activeEditor);
                     includeEditor = false;
                 }
             } else {
                 TableItem item = new TableItem(table, SWT.NONE);
                 item.setText(refs[i].getTitle());
                 item.setImage(refs[i].getTitleImage());
                 item.setData(refs[i]);
             }
         }
     }

     private void addKeyListener(final Table table, final Shell dialog) {
         table.addKeyListener(new KeyListener() {
             private boolean firstKey = true;

             private boolean quickReleaseMode = false;

             public void keyPressed(KeyEvent e) {
                 int keyCode = e.keyCode;
                 char character = e.character;
                 int accelerator = SWTKeySupport
                         .convertEventToUnmodifiedAccelerator(e);
                 KeyStroke keyStroke = SWTKeySupport
                         .convertAcceleratorToKeyStroke(accelerator);

                 // System.out.println("\nPRESSED");
 // printKeyEvent(e);
 // System.out.println("accelerat:\t" + accelerator + "\t (" +
 // KeySupport.formatStroke(Stroke.create(accelerator), true) +
 // ")");

                 boolean acceleratorForward = false;
                 boolean acceleratorBackward = false;

                 if (commandForward != null) {
                     if (forwardTriggerSequences != null) {
                         final int forwardCount = forwardTriggerSequences.length;
                         for (int i = 0; i < forwardCount; i++) {
                             final TriggerSequence triggerSequence = forwardTriggerSequences[i];

                             // Compare the last key stroke of the binding.
 final Trigger[] triggers = triggerSequence
                                     .getTriggers();
                             final int triggersLength = triggers.length;
                             if ((triggersLength > 0)
                                     && (triggers[triggersLength - 1]
                                             .equals(keyStroke))) {
                                 acceleratorForward = true;
                                 break;
                             }
                         }
                     }
                 }

                 if (commandBackward != null) {
                     if (backwardTriggerSequences != null) {
                         final int backwardCount = backwardTriggerSequences.length;
                         for (int i = 0; i < backwardCount; i++) {
                             final TriggerSequence triggerSequence = backwardTriggerSequences[i];

                             // Compare the last key stroke of the binding.
 final Trigger[] triggers = triggerSequence
                                     .getTriggers();
                             final int triggersLength = triggers.length;
                             if ((triggersLength > 0)
                                     && (triggers[triggersLength - 1]
                                             .equals(keyStroke))) {
                                 acceleratorBackward = true;
                                 break;
                             }
                         }
                     }
                 }

                 if (character == SWT.CR || character == SWT.LF) {
                     ok(dialog, table);
                 } else if (acceleratorForward) {
                     if (firstKey && e.stateMask != 0) {
                         quickReleaseMode = true;
                     }

                     int index = table.getSelectionIndex();
                     table.setSelection((index + 1) % table.getItemCount());
                 } else if (acceleratorBackward) {
                     if (firstKey && e.stateMask != 0) {
                         quickReleaseMode = true;
                     }

                     int index = table.getSelectionIndex();
                     table.setSelection(index >= 1 ? index - 1 : table
                             .getItemCount() - 1);
                 } else if (keyCode != SWT.ALT && keyCode != SWT.COMMAND
                         && keyCode != SWT.CTRL && keyCode != SWT.SHIFT
                         && keyCode != SWT.ARROW_DOWN && keyCode != SWT.ARROW_UP
                         && keyCode != SWT.ARROW_LEFT
                         && keyCode != SWT.ARROW_RIGHT) {
                     cancel(dialog);
                 }

                 firstKey = false;
             }

             public void keyReleased(KeyEvent e) {
                 int keyCode = e.keyCode;
                 int stateMask = e.stateMask;
                 // char character = e.character;
 // int accelerator = stateMask | (keyCode != 0 ? keyCode :
 // convertCharacter(character));

                 // System.out.println("\nRELEASED");
 // printKeyEvent(e);
 // System.out.println("accelerat:\t" + accelerator + "\t (" +
 // KeySupport.formatStroke(Stroke.create(accelerator), true) +
 // ")");

                 final IPreferenceStore store = WorkbenchPlugin.getDefault()
                         .getPreferenceStore();
                 final boolean stickyCycle = store
                         .getBoolean(IPreferenceConstants.STICKY_CYCLE);
                 if ((!stickyCycle && (firstKey || quickReleaseMode))
                         && keyCode == stateMask) {
                     ok(dialog, table);
                 }
             }
         });
     }

     /*
      * Add mouse listener to the table closing it when the mouse is pressed.
      */
     private void addMouseListener(final Table table, final Shell dialog) {
         table.addMouseListener(new MouseListener() {
             public void mouseDoubleClick(MouseEvent e) {
                 ok(dialog, table);
             }

             public void mouseDown(MouseEvent e) {
                 ok(dialog, table);
             }

             public void mouseUp(MouseEvent e) {
                 ok(dialog, table);
             }
         });
     }

     /**
      * Adds a listener to the given table that blocks all traversal operations.
      *
      * @param table
      * The table to which the traversal suppression should be added;
      * must not be <code>null</code>.
      */
     private final void addTraverseListener(final Table table) {
         table.addTraverseListener(new TraverseListener() {
             /**
              * Blocks all key traversal events.
              *
              * @param event
              * The trigger event; must not be <code>null</code>.
              */
             public final void keyTraversed(final TraverseEvent event) {
                 event.doit = false;
             }
         });
     }

     /*
      * Close the dialog and set selection to null.
      */
     private void cancel(Shell dialog) {
         selection = null;
         dialog.close();
     }

     /**
      * Return the backward action.
      *
      * @return the backward action
      */
     public String getBackwardActionDefinitionId() {
         return commandBackward.getCommand().getId();
     }

     /**
      * Return the forward action.
      *
      * @return the forward action
      */
     public String getForwardActionDefinitionId() {
         return commandForward.getCommand().getId();
     }

     /**
      * Returns the string which will be shown in the table header.
      */
     protected String getTableHeader() {
         return WorkbenchMessages.CyclePartAction_header;
     }

     // private static void printKeyEvent(KeyEvent keyEvent) {
 // System.out.println("keyCode:\t" + keyEvent.keyCode + "\t (" +
 // KeySupport.formatStroke(Stroke.create(keyEvent.keyCode), true) + ")");
 // System.out.println("stateMask:\t" + keyEvent.stateMask + "\t (" +
 // KeySupport.formatStroke(Stroke.create(keyEvent.stateMask), true) + ")");
 // System.out.println("character:\t" + (int) keyEvent.character + "\t (" +
 // keyEvent.character + ")");
 // }

     /*
      * Close the dialog saving the selection
      */
     private void ok(Shell dialog, final Table table) {
         TableItem[] items = table.getSelection();

         if (items != null && items.length == 1) {
             selection = items[0].getData();
         }

         dialog.close();
     }

     /*
      * Open a dialog showing all views in the activation order
      */
     private void openDialog(WorkbenchPage page) {
         final int MAX_ITEMS = 22;

         selection = null;
         final Shell dialog = new Shell(getWorkbenchWindow().getShell(),
                 SWT.MODELESS);
         Display display = dialog.getDisplay();
         dialog.setLayout(new FillLayout());

         final Table table = new Table(dialog, SWT.SINGLE | SWT.FULL_SELECTION);
         table.setHeaderVisible(true);
         table.setLinesVisible(true);
         TableColumn tc = new TableColumn(table, SWT.NONE);
         tc.setResizable(false);
         tc.setText(getTableHeader());
         addItems(table, page);
         int tableItemCount = table.getItemCount();

         switch (tableItemCount) {
         case 0:
             // do nothing;
 break;
         case 1:
             table.setSelection(0);
             break;
         default:
             table.setSelection(forward ? 1 : table.getItemCount() - 1);
         }

         tc.pack();
         table.pack();
         dialog.pack();

         Rectangle tableBounds = table.getBounds();
         tableBounds.height = Math.min(tableBounds.height, table.getItemHeight()
                 * MAX_ITEMS);
         table.setBounds(tableBounds);

         dialog.setBounds(dialog.computeTrim(tableBounds.x, tableBounds.y,
                 tableBounds.width, tableBounds.height));

         tc.setWidth(table.getClientArea().width);
         table.setFocus();
         table.addFocusListener(new FocusListener() {
             public void focusGained(FocusEvent e) {
                 // Do nothing
 }

             public void focusLost(FocusEvent e) {
                 cancel(dialog);
             }
         });

         Rectangle dialogBounds = dialog.getBounds();
         Rectangle displayBounds = display.getClientArea();
         Rectangle parentBounds = dialog.getParent().getBounds();

         // Place it in the center of its parent;
 dialogBounds.x = parentBounds.x
                 + ((parentBounds.width - dialogBounds.width) / 2);
         dialogBounds.y = parentBounds.y
                 + ((parentBounds.height - dialogBounds.height) / 2);
         if (!displayBounds.contains(dialogBounds.x, dialogBounds.y)
                 || !displayBounds.contains(dialogBounds.x + dialogBounds.width,
                         dialogBounds.y + dialogBounds.height)) {
             // Place it in the center of the display if it is not visible
 // when placed in the center of its parent;
 dialogBounds.x = (displayBounds.width - dialogBounds.width) / 2;
             dialogBounds.y = (displayBounds.height - dialogBounds.height) / 2;
         }

         dialog.setLocation(dialogBounds.x, dialogBounds.y);

         table.removeHelpListener(getHelpListener());
         table.addHelpListener(new HelpListener() {
             public void helpRequested(HelpEvent event) {
                 // Do nothing
 }
         });

         /*
          * Fetch the key bindings for the forward and backward commands. They
          * will not change while the dialog is open, but the context will. Bug
          * 55581.
          */
         final IBindingService bindingService = (IBindingService) PlatformUI
                 .getWorkbench().getAdapter(IBindingService.class);
         forwardTriggerSequences = bindingService
                 .getActiveBindingsFor(commandForward);
         backwardTriggerSequences = bindingService
                 .getActiveBindingsFor(commandBackward);

         final IContextService contextService = (IContextService) page
                 .getWorkbenchWindow().getWorkbench().getAdapter(
                         IContextService.class);
         try {
             dialog.open();
             addMouseListener(table, dialog);
             contextService.registerShell(dialog,
                     IContextService.TYPE_NONE);
             addKeyListener(table, dialog);
             addTraverseListener(table);

             while (!dialog.isDisposed()) {
                 if (!display.readAndDispatch()) {
                     display.sleep();
                 }
             }
         } finally {
             if (!dialog.isDisposed()) {
                 cancel(dialog);
             }
             contextService.unregisterShell(dialog);
             forwardTriggerSequences = null;
             backwardTriggerSequences = null;
         }
     }

     /**
      * See IPageListener
      */
     public void pageActivated(IWorkbenchPage page) {
         super.pageActivated(page);
         updateState();
     }

     /**
      * See IPageListener
      */
     public void pageClosed(IWorkbenchPage page) {
         super.pageClosed(page);
         updateState();
     }

     /**
      * See IPartListener
      */
     public void partClosed(IWorkbenchPart part) {
         super.partClosed(part);
         updateState();
     }

     /**
      * See IPartListener
      */
     public void partOpened(IWorkbenchPart part) {
         super.partOpened(part);
         updateState();
     }

     /**
      * @see Action#run()
      */
     public void runWithEvent(Event e) {
         if (getWorkbenchWindow() == null) {
             // action has been disposed
 return;
         }
         IWorkbenchPage page = getActivePage();
         openDialog((WorkbenchPage) page);
         activate(page, selection);
     }

     /**
      * Set the backward action.
      *
      * @param actionDefinitionId
      * the action
      */
     public void setBackwardActionDefinitionId(String actionDefinitionId) {
         final ICommandService commandService = (ICommandService) getWorkbenchWindow()
                 .getWorkbench().getAdapter(ICommandService.class);
         final Command command = commandService.getCommand(actionDefinitionId);
         commandBackward = new ParameterizedCommand(command, null);
     }

     /**
      * Set the forward action.
      *
      * @param actionDefinitionId
      * the action
      */
     public void setForwardActionDefinitionId(String actionDefinitionId) {
         final ICommandService commandService = (ICommandService) getWorkbenchWindow()
                 .getWorkbench().getAdapter(ICommandService.class);
         final Command command = commandService.getCommand(actionDefinitionId);
         commandForward = new ParameterizedCommand(command, null);
     }

     /**
      * Set text and tooltips in the action.
      */
     protected void setText() {
         // TBD: Remove text and tooltip when this becomes an invisible action.
 if (forward) {
             setText(WorkbenchMessages.CyclePartAction_next_text);
             setToolTipText(WorkbenchMessages.CyclePartAction_next_toolTip);
             // @issue missing action ids
 getWorkbenchWindow().getWorkbench().getHelpSystem().setHelp(this,
                     IWorkbenchHelpContextIds.CYCLE_PART_FORWARD_ACTION);
             setActionDefinitionId("org.eclipse.ui.window.nextView"); //$NON-NLS-1$
 } else {
             setText(WorkbenchMessages.CyclePartAction_prev_text);
             setToolTipText(WorkbenchMessages.CyclePartAction_prev_toolTip);
             // @issue missing action ids
 getWorkbenchWindow().getWorkbench().getHelpSystem().setHelp(this,
                     IWorkbenchHelpContextIds.CYCLE_PART_BACKWARD_ACTION);
             setActionDefinitionId("org.eclipse.ui.window.previousView"); //$NON-NLS-1$
 }
     }

     /**
      * Updates the enabled state.
      */
     protected void updateState() {
         IWorkbenchPage page = getActivePage();
         if (page == null) {
             setEnabled(false);
             return;
         }
         // enable iff there is at least one other part to switch to
 // (the editor area counts as one entry)
 int count = page.getViewReferences().length;
         if (page.getEditorReferences().length > 0) {
             ++count;
         }
         setEnabled(count >= 1);
     }

 }

